﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;

using alib;
using alib.Debugging;
using alib.Enumerable;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class identity_seq : identity_base, IIdentityId
	{
		protected identity_seq(sync_obj_ctor_arg _) : base(_) { }
		protected identity_seq(IIdentity trace)
			: base(trace)
		{
			this.mre = mre_busy;
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		ManualResetEvent mre;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		const ManualResetEvent mre_avail = default(ManualResetEvent);
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public static readonly ManualResetEvent mre_busy = new ManualResetEvent(false);
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public static readonly ManualResetEvent mre_hold = new ManualResetEvent(false);
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public static readonly ManualResetEvent mre_remove = new ManualResetEvent(false);

		public AtomicSeqState State
		{
			get
			{
				var mx = mre;
				if (mx == mre_avail)
					return AtomicSeqState.Available;
				else if (mx == mre_hold)
					return AtomicSeqState.Hold;
				else if (mx == mre_remove)
					return AtomicSeqState.Remove;
				else
					return AtomicSeqState.Transact;
			}
			set
			{
				if (value == AtomicSeqState.Hold)
					mre = mre_hold;
				else if (value == AtomicSeqState.Remove)
					mre = mre_remove;
				else
					throw new Exception();
			}
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		protected long i_seq;
		public long Id { get { return i_seq; } set { i_seq = value; } }

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool SeqIdBelow(long upper_id)
		{
			if (i_seq < 0 || upper_id == 0)
				throw new Exception();

			/// If we haven't had our sequence id assigned yet, then we know that, when it is, it will be larger than any
			/// possible sequence id that is offered for comparison.
			if (i_seq == 0)
				return false;

			return i_seq < upper_id;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool SeqIdAbove(long lower_id)
		{
			if (i_seq < 0 || lower_id == 0)
				throw new Exception();

			/// If we haven't had our sequence id assigned yet, then we know that, when it is, it will be larger than any
			/// possible sequence id that is offered for comparison.
			if (i_seq == 0)
				return true;

			return i_seq > lower_id;
		}
		/// 
		///////////////////////////////////////////////////////////////////////

		public void SetSequenceId(ref long next_obj_id)
		{
			int spins = 0;
			long id, cur;
			while (true)
			{
				cur = next_obj_id;
				if ((cur & 1) == 0 && (cur = Interlocked.CompareExchange(ref next_obj_id, cur + 1, id = cur)) == id)
				{
					i_seq = id;
					Thread.MemoryBarrier();
					next_obj_id = i_seq + 2;
					return;
				}

				if (++spins > 30)
					Thread.Yield();
				else if (spins > 5)
					Thread.SpinWait((spins - 3) << 3);	// scale backoff according to contention
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if MANUAL_SET_EVENT
		bool _promote_wait(ManualSetEvent mx)
		{
			ManualSetEvent promote = null;
#else
		bool _promote_wait(ManualResetEvent mx)
		{
			ManualResetEvent promote = null;
#endif
			while (true)
			{
				if (mx == mre_avail)
					return true;
				if (mx == mre_hold || mx == mre_remove)
					return false;
				if (mx != mre_busy)
					break;

				if ((mx = mre) != mre_busy)
					continue;
				if (promote == null)
#if MANUAL_SET_EVENT
					promote = new ManualSetEvent();
#else
					promote = new ManualResetEvent(false);
#endif
				if ((mx = mre) != mre_busy)
					continue;
				if ((mx = Interlocked.CompareExchange(ref mre, promote, mre_busy)) != mre_busy)
					continue;
				mx = promote;
				break;
			}
			if (mx == mre)
#if MANUAL_SET_EVENT
				mx.Wait();
#else
				mx.WaitOne();
#endif
			return !IsHoldOrRemove;
		}

		public bool BeginTransact()
		{
			var mx = mre;
			do
				if (!_promote_wait(mx))
					return false;
			while ((mx = Interlocked.CompareExchange(ref mre, mre_busy, mre_avail)) != mre_avail);
			return true;
		}

		public void EndTransact(AtomicSeqState s_new)
		{
#if MANUAL_SET_EVENT
			ManualSetEvent mt, mx = mre;
#else
			ManualResetEvent mt, mx = mre;
#endif
			if (mx == mre_hold || mx == mre_remove)
			{
				if (s_new != AtomicSeqState.Hold && s_new != AtomicSeqState.Remove)
					throw new Exception("A: " + s_new.ToString());
				return;
			}

			if (s_new == AtomicSeqState.Available)
				mt = mre_avail;
			else if (s_new == AtomicSeqState.Hold)
				mt = mre_hold;
			else if (s_new == AtomicSeqState.Remove)
				mt = mre_remove;
			else
				throw new Exception("B: " + s_new.ToString());

			mx = Interlocked.Exchange(ref mre, mt);

			if (mx == mre_hold || mx == mre_remove || mx == mre_avail)
				throw new Exception("C: " + s_new.ToString());

			if (mx != mre_busy)
				mx.Set();
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public bool IsHoldOrRemove
		{
			[DebuggerStepThrough]
			get { var mx = mre; return mx == mre_hold || mx == mre_remove; }
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public bool IsRemove
		{
			[DebuggerStepThrough]
			get { return mre == mre_remove; }
		}

		public bool WaitActive()
		{
			return _promote_wait(mre);
		}

		public bool TryRemove()
		{
			var mx = mre;
			return mx != mre_remove && Interlocked.CompareExchange(ref mre, mre_remove, mx) == mx;
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public String StatusCode
		{
			[DebuggerStepThrough]
			get
			{
				var mx = mre;
				if (mx == mre_avail)
					return "A";
				else if (mx == mre_hold)
					return "H";
				else if (mx == mre_remove)
					return "R";
				else if (mx == mre_busy)
					return "B";
				else
					return "W";
			}
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public static class _seq_ext
	{
		public static AtomicSeqState State(this IAtomicSequence co)
		{
			return ((identity_seq)co).State;
		}
		public static void SetState(this IAtomicSequence co, AtomicSeqState s)
		{
			((identity_seq)co).State = s;
		}
		public static String StatusCode(this IAtomicSequence co)
		{
			return ((identity_seq)co).StatusCode;
		}
		public static bool BeginTransact(this IAtomicSequence co)
		{
			return ((identity_seq)co).BeginTransact();
		}
		public static void EndTransact(this IAtomicSequence co, AtomicSeqState s)
		{
			((identity_seq)co).EndTransact(s);
		}
		public static bool IsHoldOrRemove(this IAtomicSequence co)
		{
			return ((identity_seq)co).IsHoldOrRemove;
		}
		public static bool IsRemove(this IAtomicSequence co)
		{
			return ((identity_seq)co).IsRemove;
		}
		public static bool WaitActive(this IAtomicSequence co)
		{
			return ((identity_seq)co).WaitActive();
		}
		public static bool TryRemove(this IAtomicSequence co)
		{
			return ((identity_seq)co).TryRemove();
		}
	};
}
